package tomasulo.ui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.Iterator;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;

import tomasulo.Driver;
import tomasulo.component.Adder;
import tomasulo.component.Loader;
import tomasulo.component.Multiplier;
import tomasulo.component.Register;
import tomasulo.component.Storer;
import tomasulo.unit.FU;
import tomasulo.unit.Instr;
import tomasulo.unit.Memory;

public class MainFrame extends JFrame implements ActionListener, TableModelListener {

	private static final long serialVersionUID = 1L;
	private static final int FRAME_WIDTH = 1024;
	private static final int FRAME_HEIGHT = 600;
	private static final int GAP = 20;
	
	private static final int FU_COUNT = 10;
	
	private JPanel upPanel = null;
	private JPanel midPanel = null;
	private JPanel downPanel = null;
	private UITable instructionTable = null;
	private UITable stateTable = null;
	private UITable loadTable = null;
	private UITable storeTable = null;
	private UITable memoryTable = null;
	private UITable reservationTable = null;
	private UITable fuTable = null;
	private JMenuBar menuBar = null;
	private JMenu menuFile = null;
	private JMenuItem itemLoadIns = null;
	private JMenuItem itemLoadMem = null;
	private JMenuItem itemExit = null;
	private JLabel cpuCycle = null;
	private JCheckBox cpuContinue = null;
	private JTextField cpuTotal = null;
	private JButton cpuClick = null;
	private JButton resetClick = null;
	private Chooser programChooser = null;
	private Chooser memFileChooser = null;
	
	private void initMenu() {
		menuBar = new JMenuBar();
		menuFile = new JMenu("File");
		
		programChooser = new Chooser(this, "tomasulo");
		memFileChooser = new Chooser(this, "mem");
		itemLoadIns = new JMenuItem("Load Program");
		itemLoadIns.addActionListener(this);
		itemLoadMem = new JMenuItem("Load Memory File");
		itemLoadMem.addActionListener(this);
		menuFile.add(itemLoadIns);
		menuFile.add(itemLoadMem);
		
		itemExit = new JMenuItem("Exit");
		itemExit.addActionListener(this);
		menuFile.add(itemExit);
		
		menuBar.add(menuFile);
		this.setJMenuBar(menuBar);
	}
	
	private void initFrame() {
		this.setSize(FRAME_WIDTH, FRAME_HEIGHT);
		this.setTitle("Tomasulo");
		this.setResizable(false);
		this.setDefaultCloseOperation(EXIT_ON_CLOSE);
		upPanel = new JPanel(new GridLayout(1, 3, GAP, GAP));
		midPanel = new JPanel(new BorderLayout(GAP, GAP));
		downPanel = new JPanel(new GridLayout(1, 3, GAP, GAP));
		initMenu();
	}
	
	private void initInstructionTable() {
		String title[] = {"Name", "Dest", "Operand 1", "Operand 2"};
		instructionTable = new UITable(20, 4, title, new UITableEditable());
		instructionTable.setTableTitle("Instructions");
		upPanel.add(instructionTable.getTablePane());
	}
	
	private void initStateTable() {
		String title[] = {"Issue", "Exec Comp", "Write Result"};
		stateTable = new UITable(20, 3, title, new UITableEditable());
		stateTable.setTableTitle("Running State");
		upPanel.add(stateTable.getTablePane());
	}
	
	private void initFUTable() {
		String title[] = new String[FU_COUNT];
		for (int i = 0; i < FU_COUNT; ++i) {
			title[i] = new String("F" + i);
		}
		fuTable = new UITable(1, FU_COUNT, title, new UITableEditable());
		fuTable.setTableTitle("Float Unit Registers");
		downPanel.add(fuTable.getTablePane());
	}
	
	private void schedulePanels() {
		upPanel.setPreferredSize(new Dimension(upPanel.getWidth(), (int)(FRAME_HEIGHT * 0.35)));
		midPanel.setPreferredSize(new Dimension(midPanel.getWidth(), (int)(FRAME_HEIGHT * 0.5)));
		downPanel.setPreferredSize(new Dimension(downPanel.getWidth(), (int)(FRAME_HEIGHT * 0.15)));
		this.getContentPane().add(upPanel, BorderLayout.NORTH);
		this.getContentPane().add(midPanel, BorderLayout.CENTER);
		this.getContentPane().add(downPanel, BorderLayout.SOUTH);
	}
	
	private void initLoadAndStoreTable() {
		JPanel p = new JPanel(new GridLayout(2, 1, GAP, GAP / 2));
		
		String t1[] = {"", "Time", "Busy", "Address", "Cache"};
		loadTable = new UITable(3, 5, t1, new UITableEditable());
		loadTable.setTableTitle("Load Queue");
		loadTable.setAt(0, 0, "Load1");
		loadTable.setAt(1, 0, "Load2");
		loadTable.setAt(2, 0, "Load3");
		p.add(loadTable.getTablePane());
		
		String t2[] = {"", "Time", "Busy", "Address", "Qi"};
		storeTable = new UITable(3, 5, t2, new UITableEditable());
		storeTable.setTableTitle("Store Queue");
		storeTable.setAt(0, 0, "Store1");
		storeTable.setAt(1, 0, "Store2");
		storeTable.setAt(2, 0, "Store3");
		p.add(storeTable.getTablePane());
		upPanel.add(p);
	}
	
	private void initMemoryTable() {
		String title[] = {"Address", "Value"};
		memoryTable = new UITable(Memory.DEFAULT_CAPACITY, 2, title, new UITableEditable() {
			public boolean isEditable(int row, int column) {
				return column == 1;
			}
		});
		memoryTable.setTableTitle("Memroy");
		JPanel j = memoryTable.getTablePane();
		j.setPreferredSize(new Dimension(FRAME_WIDTH / 3, j.getHeight()));
		int maxHexPos = 0;
		while (((Memory.DEFAULT_CAPACITY - 1) >> (maxHexPos * 4)) > 0) {
			++maxHexPos;
		}
		for (int i = 0; i < Memory.DEFAULT_CAPACITY; ++i) {
			String str = Integer.toHexString(i);
			while (str.length() < maxHexPos) {
				str = "0" + str;
			}
			str = "0x" + str;
			memoryTable.setAt(i, 0, str);
			memoryTable.setAt(i, 1, Float.toString(0));
		}
		memoryTable.addTableModelListener(this);
		midPanel.add(memoryTable.getTablePane(), BorderLayout.WEST);
	}
	
	private JPanel initControlPanel() {
		JPanel p = new JPanel(new GridLayout(2, 1));
		JPanel pa = new JPanel(new FlowLayout());
		JPanel pb = new JPanel(new FlowLayout());
		
		cpuCycle = new JLabel("0");
		Font font = new Font("", Font.PLAIN, 40);
		cpuCycle.setFont(font);
		pa.add(new JLabel("Clock cycle:"));
		pa.add(cpuCycle);
		
		cpuTotal = new JTextField();
		Dimension d = cpuTotal.getPreferredSize();
		d.width = 100;
		cpuTotal.setPreferredSize(d);
		cpuContinue = new JCheckBox("Continuous Run");
		cpuClick = new JButton("Trigger");
		cpuClick.addActionListener(this);
		resetClick = new JButton("Reset");
		resetClick.addActionListener(this);
		pb.add(resetClick);
		pb.add(new JLabel("Count:"));
		pb.add(cpuTotal);
		pb.add(cpuContinue);
		pb.add(cpuClick);
		
		p.add(pa);
		p.add(pb);
		return p;
	}
	
	private void initReservationAndControlTable() {
		JPanel p = new JPanel(new GridLayout(2, 1));
		String title[] = {"Time", "Name", "Busy", "Operator", "I", "J", "Result"};
		reservationTable = new UITable(5, 7, title, new UITableEditable());
		reservationTable.setTableTitle("Reservation Station");
		p.add(reservationTable.getTablePane());
		p.add(initControlPanel());
		reservationTable.setAt(0, 1, Driver.getAdder1().toString());
		reservationTable.setAt(1, 1, Driver.getAdder2().toString());
		reservationTable.setAt(2, 1, Driver.getAdder3().toString());
		reservationTable.setAt(3, 1, Driver.getMultiplier1().toString());
		reservationTable.setAt(4, 1, Driver.getMultiplier2().toString());
		midPanel.add(p, BorderLayout.CENTER);
	}
	
	private void updateFLOS() {
		if (!Driver.getFLOS().isUpToDate()) {
			Iterator<Instr> iter = Driver.getFLOS().getIterator();
			int count = 0;
			while (iter.hasNext()) {
				Instr ins = iter.next();
				String insPart[] = ins.instr.split("[ \t]*[, \t][ \t]*");
				instructionTable.setAt(count, 0, insPart[0]);
				instructionTable.setAt(count, 1, insPart[1]);
				instructionTable.setAt(count, 2, insPart[2]);
				if (insPart.length > 3) {
					instructionTable.setAt(count, 3, insPart[3]);
				}
				++count;
			}
			Driver.getFLOS().setUpToDate(true);
		}
	}
	
	private void updateMemory() {
		Iterator<Integer> iter = Driver.getMemory().getChangeListIterator();
		while (iter.hasNext()) {
			int row = iter.next();
			memoryTable.setAt(row, 1, Float.toString(Driver.getMemory().getAt(row)));
		}
		Driver.getMemory().clearChangeList();
	}
	
	private void updateCPUCycle() {
		cpuCycle.setText(Integer.toString(Driver.getCPU().getTick()));
	}
	
	private void updateFU() {
		FU fu = Driver.getFU();
		for (int i = 0; i < FU_COUNT; ++i) {
			Register r = fu.get(i);
			if (r.isReady()) {
				this.fuTable.setAt(0, i, Float.toString(r.getValue()));
			} else {
				this.fuTable.setAt(0, i, r.getStation());
			}
		}
	}
	
	private void updateOneLoader(Loader l, int tableIndex) {
		this.loadTable.setAt(tableIndex, 1, Integer.toString(l.getTime()));
		this.loadTable.setAt(tableIndex, 2, Boolean.toString(l.isBusy()));
		this.loadTable.setAt(tableIndex, 3, Integer.toString(l.getAddr(), 16));
		if (l.getCache() != null) {
			this.loadTable.setAt(tableIndex, 4, Float.toString(l.getCache()));
		} else {
			this.loadTable.setAt(tableIndex, 4, "N/A");
		}
	}
	
	private void updateLoader() {
		updateOneLoader(Driver.getLoader1(), 0);
		updateOneLoader(Driver.getLoader2(), 1);
		updateOneLoader(Driver.getLoader3(), 2);
	}
	
	private void updateOneStorer(Storer l, int tableIndex) {
		if (l.getTime() >= 0)
			this.storeTable.setAt(tableIndex, 1, Integer.toString(l.getTime()));
		else
			this.storeTable.setAt(tableIndex, 1, "Wait");
		this.storeTable.setAt(tableIndex, 2, Boolean.toString(l.isBusy()));
		this.storeTable.setAt(tableIndex, 3, Integer.toString(l.getAddr(), 16));
		Register r = l.getQ();
		if (r == null) {
			this.storeTable.setAt(tableIndex, 4, "N/A");
		} else if (r.isReady()) {
			this.storeTable.setAt(tableIndex, 4, Float.toString(r.getValue()));
		} else {
			this.storeTable.setAt(tableIndex, 4, r.getStation());
		}
	}
	
	private void updateStorer() {
		updateOneStorer(Driver.getStorer1(), 0);
		updateOneStorer(Driver.getStorer2(), 1);
		updateOneStorer(Driver.getStorer3(), 2);
	}
	
	private void updateOneAdder(Adder a, int tableIndex) {
		if (a.getTime() >= 0)
			this.reservationTable.setAt(tableIndex, 0, Integer.toString(a.getTime()));
		else
			this.reservationTable.setAt(tableIndex, 0, "Wait");
		this.reservationTable.setAt(tableIndex, 2, Boolean.toString(a.isBusy()));
		this.reservationTable.setAt(tableIndex, 3, a.getOp().toString());
		Register r = a.getOp1();
		if (r == null) {
			this.reservationTable.setAt(tableIndex, 4, "N/A");
		} else if (r.isReady()) {
			this.reservationTable.setAt(tableIndex, 4, Float.toString(r.getValue()));
		} else {
			this.reservationTable.setAt(tableIndex, 4, r.getStation());
		}
		r = a.getOp2();
		if (r == null) {
			this.reservationTable.setAt(tableIndex, 5, "N/A");
		} else if (r.isReady()) {
			this.reservationTable.setAt(tableIndex, 5, Float.toString(r.getValue()));
		} else {
			this.reservationTable.setAt(tableIndex, 5, r.getStation());
		}
		if (a.getResult() != null) {
			this.reservationTable.setAt(tableIndex, 6, Float.toString(a.getResult()));
		} else {
			this.reservationTable.setAt(tableIndex, 6, "N/A");
		}
	}
	
	private void updateOneMultiplier(Multiplier a, int tableIndex) {
		if (a.getTime() >= 0)
			this.reservationTable.setAt(tableIndex, 0, Integer.toString(a.getTime()));
		else
			this.reservationTable.setAt(tableIndex, 0, "Wait");
		this.reservationTable.setAt(tableIndex, 2, Boolean.toString(a.isBusy()));
		this.reservationTable.setAt(tableIndex, 3, a.getOp().toString());
		Register r = a.getOp1();
		if (r.isReady()) {
			this.reservationTable.setAt(tableIndex, 4, Float.toString(r.getValue()));
		} else {
			this.reservationTable.setAt(tableIndex, 4, r.getStation());
		}
		r = a.getOp2();
		if (r.isReady()) {
			this.reservationTable.setAt(tableIndex, 5, Float.toString(r.getValue()));
		} else {
			this.reservationTable.setAt(tableIndex, 5, r.getStation());
		}
		if (a.getResult() != null) {
			this.reservationTable.setAt(tableIndex, 6, Float.toString(a.getResult()));
		} else {
			this.reservationTable.setAt(tableIndex, 6, "N/A");
		}
	}

	private void updateReservation() {
		updateOneAdder(Driver.getAdder1(), 0);
		updateOneAdder(Driver.getAdder2(), 1);
		updateOneAdder(Driver.getAdder3(), 2);
		updateOneMultiplier(Driver.getMultiplier1(), 3);
		updateOneMultiplier(Driver.getMultiplier2(), 4);
	}
	
	private void updateState() {
		Iterator<Instr> iter = Driver.getFLOS().getIterator();
		for (int idx = 0; iter.hasNext(); ++idx) {
			updateOneInstr(iter.next(), idx);
		}
	}
	
	private void updateOneInstr(Instr instr, int tableIndex) {
		if (instr.getTimeIssue() > 0)
			this.stateTable.setAt(tableIndex, 0, Integer.toString(instr.getTimeIssue()));
		else
			this.stateTable.setAt(tableIndex, 0, "");
		if (instr.getTimeComp() > 0)
			this.stateTable.setAt(tableIndex, 1, Integer.toString(instr.getTimeComp()));
		else
			this.stateTable.setAt(tableIndex, 1, "");
		if (instr.getTimeWrite() > 0)
			this.stateTable.setAt(tableIndex, 2, Integer.toString(instr.getTimeWrite()));
		else
			this.stateTable.setAt(tableIndex, 2, "");
	}

	private void loadMemory(File f) {
		try {
			BufferedReader reader = new BufferedReader(new FileReader(f));
			String ins;
			float cur;
			for (int i = 0; i < Memory.DEFAULT_CAPACITY; ++i) {
				ins = reader.readLine();
				if (ins == null || ins.equals("")) {
					break;
				}
				cur = Float.parseFloat(ins);
				memoryTable.setAt(i, 1, Float.toString(cur));
			}
			reader.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public MainFrame() {
		super();
		initFrame();
		initInstructionTable();
		initStateTable();
		initLoadAndStoreTable();
		initMemoryTable();
		initReservationAndControlTable();
		initFUTable();
		schedulePanels();
		this.setVisible(true);
		updateUI();
	}

	@Override
	public void actionPerformed(ActionEvent arg0) {
		if (itemLoadIns.equals(arg0.getSource())) {
			File f = programChooser.getFile();
			if (f != null) {
				instructionTable.clear();
				Driver.getFLOS().loadInstr(f);
				updateUI();
			}
		} else if (itemExit.equals(arg0.getSource())) {
			System.exit(0);
		} else if (cpuClick.equals(arg0.getSource())) {
			if (cpuContinue.isSelected()) {
				int count = 0;
				try {
					count = Integer.parseInt(cpuTotal.getText());
					for (int i = 0; i < count; ++i) {
						Driver.getCPU().drive();
					}
				} catch (Exception e) {
					
				}
			} else {
				Driver.getCPU().drive();
			}
		} else if (itemLoadMem.equals(arg0.getSource())) {
			File f = memFileChooser.getFile();
			if (f != null) {
				loadMemory(f);
			}
		} else if (resetClick.equals(arg0.getSource())) {
			Driver.getCPU().reset();
			Driver.getFLOS().reset();
			Driver.getMemory().reset();
			updateUI();
		}
	}
	
	public void updateUI() {
		updateFLOS();
		updateMemory();
		updateCPUCycle();
		updateFU();
		updateLoader();
		updateStorer();
		updateReservation();
		updateState();
	}

	@Override
	public void tableChanged(final TableModelEvent arg0) {
		String s = memoryTable.getAt(arg0.getFirstRow(), arg0.getColumn());
		try {
			float num = Float.parseFloat(s);
			Driver.getMemory().setAt(arg0.getFirstRow(), num, false);
		} catch (Exception e) {
			memoryTable.setAt(arg0.getFirstRow(), arg0.getColumn(), "0.0");
		}
	}
	
}
